﻿using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;

namespace Kingzad.MicroService.BusinessCore.Providers
{
    public interface ICacheProvider
    {
        Task<bool> AddAsync(string key, object value, long durationSeconds = 0);
        Task<T> GetAsync<T>(string key);
        Task<IDictionary<string, object>> GetAsync(IEnumerable<string> keys);
        Task<bool> RemoveAsync(string key);
    }

    public class CacheProvider : ICacheProvider
    {
        private readonly ILogger<CacheProvider> _logger;
        private readonly IDistributedCache _distributedCache;
        private readonly string _appName;
        //private readonly IOptions<FacebookServiceOptions> _options;

        public CacheProvider(
            ILogger<CacheProvider> logger,
            IDistributedCache distributedCache)
        {
            _logger = logger;
            _distributedCache = distributedCache;
            //_options = options;
            _appName = "Micro-Service";
        }

        public async Task<bool> AddAsync(string key, object value, long durationSeconds = 0)
        {
            try
            {
                var cacheKey = $"{_appName}:{key}";
                if (durationSeconds == 0)
                {
                    await _distributedCache.SetAsync(cacheKey, ObjectToByteArray(value)).ConfigureAwait(false);
                }
                else
                {
                    var cacheEntryOptions = new DistributedCacheEntryOptions()
                    {
                        AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(durationSeconds)
                    };
                    await _distributedCache.SetAsync(cacheKey, ObjectToByteArray(value), cacheEntryOptions).ConfigureAwait(false);
                }

                _logger.LogDebug($"Added item to Cache using CacheKey: {cacheKey} DurationSeconds: {durationSeconds}");

                return true;
            }
            catch (Exception e)
            {
                _logger.LogError(e, $"Error add key: {key}, value: {value}", e.Message);
                return false;
            }
        }

        public async Task<T> GetAsync<T>(string key)
        {
            var cacheKey = $"{_appName}:{key}";
            try
            {
                var cacheData = await _distributedCache.GetAsync(cacheKey).ConfigureAwait(false);
                if (cacheData is null)
                {
                    _logger.LogDebug($"Read null from Cache using CacheKey: {cacheKey}");

                    return default;
                }
                var obj = ByteArrayToObject<T>(cacheData);
                _logger.LogDebug($"Read value from Cache using CacheKey: {cacheKey}");
                return obj;
            }
            catch (Exception e)
            {
                _logger.LogError(e, $"Error while retrieving value for key {cacheKey}", e.Message);
                return default;
            }
        }

        public async Task<IDictionary<string, object>> GetAsync(IEnumerable<string> keys)
        {
            try
            {
                var keyValues = new Dictionary<string, object>();
                foreach (string key in keys)
                {
                    keyValues[key] = await GetAsync<Object>(key).ConfigureAwait(false);
                }
                return keyValues;
            }
            catch (Exception e)
            {
                _logger.LogError(e, $"Error while retrieving for key {keys}", e.Message);
                return new Dictionary<string, object>();
            }
        }

        public async Task<bool> RemoveAsync(string key)
        {
            try
            {
                var cacheKey = $"{_appName}:{key}";
                await _distributedCache.RemoveAsync(cacheKey).ConfigureAwait(false);

                _logger.LogDebug($"Removed item from Cache using CacheKey: {cacheKey}");

                return true;
            }
            catch (Exception e)
            {
                _logger.LogError(e, $"Error wile remove key {key}", e.Message);
                return default;
            }
        }

        private byte[] ObjectToByteArray(object obj)
        {
            if (obj == null)
            {
                return new byte[0];
            }

            var bf = new BinaryFormatter();
            using (var ms = new MemoryStream())
            {
                bf.Serialize(ms, obj);
                return ms.ToArray();
            }
        }

        private T ByteArrayToObject<T>(byte[] bytes)
        {
            if (bytes == null)
            {
                return default;
            }

            var bf = new BinaryFormatter();
            using (var ms = new MemoryStream(bytes))
            {
                var obj = bf.Deserialize(ms);
                if (obj is T result)
                {
                    return result;
                }

                return default;
            }
        }
    }
}
